﻿Imports System.ComponentModel

''' <summary>
''' This class created as base class to display time series data in properties grid; there is one series instance per curve
''' </summary>
<DefaultProperty("Label")> _
Friend MustInherit Class clsSeries

    Protected _SeriesType As enumGraphType
    Protected _Zed As ZedGraph.ZedGraphControl
    Protected _LineType As enumLineType
    Protected _SymbolType As enumSymbolType
    Protected _ZedCurve As ZedGraph.LineItem
    Protected _ZedCurveMeas As ZedGraph.LineItem 'only used for calibration type tables for measured data
    Protected _IsCalc As Boolean
    Protected _ZedText As New Generic.List(Of ZedGraph.TextObj)
    Protected _NeedsReload As Boolean 'set to true if one of the variables that controls what data to acquire change (in the inherited class)
    Protected _StatsText As String = ""
    Protected _StatsVisible As Boolean = False
    Protected _StatsPosition As enumNotePosition = enumNotePosition.TopRight
    Protected _StatsFont As Font
    Protected _StatsAlignment As enumNoteAlignment = enumNoteAlignment.Center
    Protected _StatsBorder As Boolean = True
    Protected _Table As clsTable
    Protected _StationID, _PCode As String
    Protected _StationList, _PCodeList As String 'semi-colon separated list of valid items
    'following used for profile and scatter plots only
    Protected _DateList As String 'semi-colon separated list of valid items
    Protected _DateTime As Date 'used for profile plots only
    Protected _TimeWindow As enumTimeWindow
    Protected _TimeWindowMin As Double() = {0.001, 1, 5, 15, 30, 60, 120, 360, 720, 1440, 4320, 10080, 20160, 43200, 129600, 259200, 525600, 52000000}

    ''' <summary>
    ''' Class that contains additional selection criteria
    ''' </summary>
    Friend Property MoreCriteria As New clsMoreCriteria

    Friend Enum enumLineType
        None = -1
        Solid
        Dash
        Dot
        DashDot
        DashDotDot
        Bars
    End Enum

    Friend Enum enumSymbolType
        None = -1
        Circle
        Square
        Diamond
        Triangle
        Tri_Down
        Star
        XCross
        Plus
        HDash
        VDash
        Box_Whisker
    End Enum

    Protected Structure structRCode
        Dim RCode As String
        Dim Calc_Stats As Boolean
        Dim NonDetect_PCode As Boolean
        Dim NonDetect_Result As Boolean
        Dim Detect_Mult As Double
    End Structure

    ''' <summary>
    ''' List of available X axes
    ''' </summary>
    Friend Enum enumXAxis
        ''' <summary>X Axis plotted on bottom</summary>
        Bottom
        ''' <summary>X Axis plotted on top</summary>
        Top
    End Enum

    ''' <summary>
    ''' List of available Y axes
    ''' </summary>
    Friend Enum enumYAxis
        ''' <summary>Y Axis plotted on left</summary>
        Left
        ''' <summary>Y Axis plotted on right</summary>
        Right
    End Enum

    ''' <summary>
    ''' List of available Y axis indices
    ''' </summary>
    Friend Enum enumYAxisIndex
        Axis_1
        Axis_2
        Axis_3
        Axis_4
        Axis_5
    End Enum

    ''' <summary>
    ''' Construct new series instance and set default line width and symbol
    ''' </summary>
    ''' <param name="Zed">Existing ZedGraph control</param>
    Friend Sub New(ByRef Zed As ZedGraph.ZedGraphControl, ByVal SeriesType As enumGraphType)
        _Zed = Zed
        _SeriesType = SeriesType
        _ZedCurve = New ZedGraph.LineItem("")
        _ZedCurveMeas = New ZedGraph.LineItem("")
        _Zed.GraphPane.CurveList.Insert(0, _ZedCurve)
        _TimeWindow = ActiveGraphTab.AnimationInfo.TimeWindow
        _SymbolType = enumSymbolType.Circle
        _LineType = enumLineType.Solid
        _StatsFont = New Font("Tahoma", 8)
        Reset()
    End Sub

    Friend Function Clone() As clsSeries
        Return MemberwiseClone()
    End Function

    ''' <summary>
    ''' ZedCurve item containing the main data for the series
    ''' </summary>
    Friend Property ZedCurve() As ZedGraph.LineItem
        Get
            Return _ZedCurve
        End Get
        Set(ByVal value As ZedGraph.LineItem)
            _ZedCurve = value
        End Set
    End Property

    ''' <summary>
    ''' ZedCurve item containing the measured data for the calibration series
    ''' </summary>
    Friend Property ZedCurveMeas() As ZedGraph.LineItem
        Get
            Return _ZedCurveMeas
        End Get
        Set(ByVal value As ZedGraph.LineItem)
            _ZedCurveMeas = value
        End Set
    End Property

    ''' <summary>
    ''' Add ZedCurve to curvelist in graph pane
    ''' </summary>
    ''' <remarks></remarks>
    Friend Sub AddZedCurve()
        _Zed.GraphPane.CurveList.Add(_ZedCurve)
        If _Table IsNot Nothing AndAlso _Table.TableType = clsTable.enumTableType.Calibration Then
            _Zed.GraphPane.CurveList.Add(_ZedCurveMeas)
        End If
    End Sub

    ''' <summary>
    ''' Remove ZedCurve from curvelist in graph pane
    ''' </summary>
    ''' <remarks></remarks>
    Friend Sub RemoveZedCurve()
        _Zed.GraphPane.CurveList.Remove(_ZedCurve)
        If _Table IsNot Nothing AndAlso _Table.TableType = clsTable.enumTableType.Calibration Then
            _Zed.GraphPane.CurveList.Remove(_ZedCurveMeas)
        End If
    End Sub

    ''' <summary>
    ''' Label associated with series
    ''' </summary>
    <DisplayName("1. Series Label"), Category("(Data)"), Description("Sets the label associated with this series")> _
    Public Overridable Property Label() As String
        Get
            Return _ZedCurve.Label.Text
        End Get
        Set(ByVal value As String)
            _ZedCurve.Label.Text = value
        End Set
    End Property

    <DisplayName("2. Is Visible?"), Category("(Data)"), Description("Is Series visible?")> _
    Public Property Visible() As Boolean
        Get
            Return _ZedCurve.IsVisible
        End Get
        Set(ByVal value As Boolean)
            _ZedCurve.IsVisible = value
            _ZedCurve.Label.IsVisible = value
            _ZedCurveMeas.IsVisible = value
            _ZedCurveMeas.Label.IsVisible = value
        End Set
    End Property

    ''' <summary>
    ''' Is this a calced series? If true, the data points are only stored in the ZedCurve and the Table is Nothing
    ''' </summary>
    <DisplayName("3. Is Calculated?"), Category("(Data)"), Description("Indicates if this is a calculated series (read-only)"), [ReadOnly](True)> _
    Public Property IsCalc() As Boolean
        Get
            Return _IsCalc
        End Get
        Set(ByVal value As Boolean)
            _IsCalc = value
        End Set
    End Property

    <DisplayName("4. Table Name"), Category("(Data)"), Description("Table name associated with series (read-only)"), [ReadOnly](True)> _
    Public Overridable Property Table() As clsTable
        Get
            Return _Table
        End Get
        Set(ByVal value As clsTable)
            _Table = value
        End Set
    End Property

    ''' <summary>
    ''' Station ID associated with series (for longitudinal profiles, this is the Branch Name to plot)
    ''' </summary>
    <DisplayName("5. Station ID"), Category("(Data)"), Description("Station ID associated with series (read-only)"), [ReadOnly](True)> _
    Public Property StationID() As String
        Get
            Return _StationID
        End Get
        Set(ByVal value As String)
            StatsVisible = False
            _StationID = value
            _NeedsReload = True
        End Set
    End Property

    ''' <summary>
    ''' PCode associated with series
    ''' </summary>
    <DisplayName("6. PCode"), Category("(Data)"), Description("PCode associated with series (read-only)"), [ReadOnly](True)> _
    Public Property PCode() As String
        Get
            Return _PCode
        End Get
        Set(ByVal value As String)
            StatsVisible = False
            _PCode = value
            _NeedsReload = True
        End Set
    End Property

    ''' <summary>
    ''' PCode associated with series
    ''' </summary>
    <DisplayName("7. Other Criteria"), Category("(Data)"), Description("Additional selection criteria for series (read-only)"), [ReadOnly](True)> _
    Public ReadOnly Property MoreCriteriaText() As String
        Get
            If MoreCriteria Is Nothing Then Return "None" Else Return MoreCriteria.ToString
        End Get
    End Property

    ''' <summary>
    ''' Date-time associated with series
    ''' </summary>
    <DisplayName("8. Date-Time"), Category("(Data)"), Description("Date-time value associated with profile series (read-only)"), [ReadOnly](True)> _
    Public Property DateTime() As String
        Get
            Return _DateTime.ToString(MyDateTimeFormat)
        End Get
        Set(ByVal value As String)
            If IsDate(value) Then
                _DateTime = CDate(value)
                _NeedsReload = True
            End If
        End Set
    End Property

    ''' <summary>
    ''' Time window associated with series
    ''' </summary>
    <DisplayName("9. Time Window"), Category("(Data)"), Description("Time window associated with profile series (read-only)"), [ReadOnly](True)> _
    Public Property TimeWindow() As enumTimeWindow
        Get
            Return _TimeWindow
        End Get
        Set(ByVal value As enumTimeWindow)
            _TimeWindow = value
            _NeedsReload = True
        End Set
    End Property

    ''' <summary>
    ''' List of valid Station IDs associated with series (semi-colon separated) (doesn't apply to long. profiles)
    ''' </summary>
    Friend Property StationList() As String
        Get
            Return _StationList
        End Get
        Set(ByVal value As String)
            _StationList = value
        End Set
    End Property

    ''' <summary>
    ''' List of valid PCodes associated with series (semi-colon separated)
    ''' </summary>
    Friend Property PCodeList() As String
        Get
            Return _PCodeList
        End Get
        Set(ByVal value As String)
            _PCodeList = value
        End Set
    End Property

    ''' <summary>
    ''' Given new StationID, find the unique PCodes that match so can update PCodeList
    ''' </summary>
    Friend Function GetPCodeList(ByVal StationID As String) As String
        Try
            If _IsCalc OrElse _Table.TableType = clsTable.enumTableType.BMD OrElse _Table.TableType = clsTable.enumTableType.WRDBData OrElse _Table.TableType = clsTable.enumTableType.Calibration Then Return PCodeList
            Application.UseWaitCursor = True
            Application.DoEvents()
            Dim PCodes As String = ""
            Dim dr As DbDataReader = _Table.WRDBProject.DB.ExecuteReader("SELECT DISTINCT PCode FROM {0} WHERE Station_ID='{1}'", _Table.WRDBProject.DB.AddBrackets(_Table.TableName), StationID)
            While dr IsNot Nothing AndAlso dr.Read
                If Not dr.IsDBNull(0) Then PCodes &= IIf(PCodes = "", "", ";") & dr.GetString(0)
            End While
            dr.Close()
            Return PCodes
        Catch ex As Exception
            ErrorMsg(, ex)
            Return ""
        Finally
            Application.UseWaitCursor = False
        End Try
    End Function

    ''' <summary>
    ''' List of valid dates associated with series (semi-colon separated) (applies only to profiles)
    ''' </summary>
    Friend Property DateList() As String
        Get
            Return _DateList
        End Get
        Set(ByVal value As String)
            _DateList = value
        End Set
    End Property

    ''' <summary>
    ''' Given StationID and PCode, find the unique dates so can update DateList
    ''' </summary>
    Friend Function GetDateList(ByVal StationID As String, ByVal PCode As String) As String
        Try
            If _IsCalc OrElse _Table.TableType = clsTable.enumTableType.BMD OrElse _Table.TableType = clsTable.enumTableType.WRDBData OrElse _Table.TableType = clsTable.enumTableType.Calibration Then Return DateList
            Application.UseWaitCursor = True
            Application.DoEvents()
            Dim Dates As String = ""
            Dim dr As DbDataReader = _Table.WRDBProject.DB.ExecuteReader("SELECT DISTINCT Date_Time FROM {0} WHERE Station_ID='{1}' AND PCode='{2}'", _Table.WRDBProject.DB.AddBrackets(_Table.TableName), StationID, PCode)
            While dr IsNot Nothing AndAlso dr.Read
                If Not dr.IsDBNull(0) Then Dates &= IIf(Dates = "", "", ";") & CDate(dr.GetValue(0)).ToString(MyDateTimeFormat)
            End While
            dr.Close()
            Return Dates
        Catch ex As Exception
            ErrorMsg(, ex)
            Return ""
        Finally
            Application.UseWaitCursor = False
        End Try
    End Function

    ''' <summary>
    ''' When forming new series, call this to set the default series name
    ''' </summary>
    Friend Overridable Sub GetDefaultLabel()
        Label = String.Format("{0} at {1}", _PCode, _StationID)
    End Sub

    Friend Overridable Sub GetData()
        Try
            'reset counters
            Table.NumNonCalc = 0
            Table.NumNonDetect = 0
            Table.NumExceed = 0

            For Each obj As ZedGraph.TextObj In _ZedText
                _Zed.GraphPane.GraphObjList.Remove(obj)
                obj = Nothing
            Next
            _ZedText.Clear()

        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    ' ''' <summary>
    ' ''' This will interpret rcode and adjust result if is non detect; will also increment counters; returns False if is NonCalc value which should not be added
    ' ''' </summary>
    ' ''' <param name="StaID">Station ID associated with observation (will test for null value)</param>
    ' ''' <param name="DateTime">Date-time associated with observation (will test for null value)</param>
    ' ''' <param name="PCode">PCode associated with observation (will test for null value)</param>
    ' ''' <param name="RCode">RCode associated with observation (will test for null value)</param>
    ' ''' <param name="Result">Numerical observation  (will test for null value)</param>
    ' ''' <param name="NewResult">Updated value depending on RCode and PCode</param>
    ' ''' <returns>Will return False only if unable to compute Result (not if criteria could not be found)</returns>
    'Friend Function GetResult(StaID As Object, PCode As Object, DateTime As Date, RCode As Object, Result As Object, ByRef NewResult As Double) As Boolean
    '    StaID = TestNull(StaID, "")
    '    PCode = TestNull(PCode, "")
    '    DateTime = TestNull(DateTime, CDate("1/1/1900"))
    '    RCode = TestNull(RCode, "")
    '    Result = TestNull(Result, Double.MinValue)
    '    NewResult = Result 'will return result value unless rcode is a non-detect value
    '    Dim MinCrit, MaxCrit As Double, IsNonDetect As Boolean = False, IsCalcStats As Boolean = True

    '    'note: Criteria class instantiated in clsTable when tablename is assigned

    '    Dim CritFound As Boolean = Table.Criteria IsNot Nothing AndAlso Table.Criteria.TryGetCriteria(StaID, PCode, DateTime, RCode, NewResult, MinCrit, MaxCrit, , IsNonDetect, IsCalcStats)

    '    If RCode = "" And Result = Double.MinValue Then 'null result and empty rcode--treat as non-calc value
    '        Table.NumNonCalc += 1
    '        Return False
    '    ElseIf CritFound Then
    '        If NewResult < MinCrit Or NewResult > MaxCrit Then Table.NumExceed += 1
    '        If IsNonDetect Then Table.NumNonDetect += 1
    '        Return True
    '    ElseIf IsCalcStats Then
    '        If IsNonDetect Then Table.NumNonDetect += 1
    '        Return True
    '    Else
    '        Table.NumNonCalc += 1
    '        Return False
    '    End If
    'End Function

    ''' <summary>
    ''' Replace the table associated with this series; if the new table doesn't have the previous StationID/PCode available, they will be reset
    ''' </summary>
    ''' <param name="NewTable">New table to assign</param>
    ''' <remarks></remarks>
    Friend Sub ReplaceTable(ByVal NewTable As clsTable)
        Table = NewTable
        Dim lst As Generic.List(Of String)
        If TypeOf Me Is clsSeriesTimeSeries Then
            lst = Table.GetStationList("All Branches")
        Else
            lst = Table.GetBranchList()
        End If

        Dim StationList As String = ""
        For Each s As String In lst
            StationList &= IIf(StationList = "", "", ";") & StringSplit(s, " - ")(0)
        Next
        lst.Clear()
        lst.AddRange(StationList.Split(";"))
        If Not lst.Contains(StationID) Then StationID = StringSplit(lst(0), " - ")(0)
        StationList = StationList

        lst = Table.GetPCodeList(StationID)
        PCodeList = ListToString(lst)
        If Not lst.Contains(PCode) AndAlso lst.Count > 0 Then PCode = lst(0)

        If TypeOf Me Is clsSeriesScatter Then
            With CType(Me, clsSeriesScatter)
                lst = .Table.GetPCodeList(.StationID)
                .PCodeListX = ListToString(lst)
                If Not lst.Contains(.PCodeX) AndAlso lst.Count > 0 Then .PCodeX = lst(0)
            End With
        End If

        Select Case _SeriesType
            Case enumGraphType.Depth, enumGraphType.Profile, enumGraphType.Scatter, enumGraphType.Width
                DateList = GetDateList(StationID, PCode)
            Case Else
                DateList = ""
        End Select

        GetData()
    End Sub

    Friend Overridable Sub Reset()
        XAxis = enumXAxis.Bottom
        YAxis = enumYAxis.Left
        YAxisIndex = enumYAxisIndex.Axis_1
        SmoothCurve = False
        LineType = Drawing2D.DashStyle.Solid
        LineWidth = 1
        SymbolType = enumSymbolType.None
        SymbolSize = 4
        SymbolFill = False
    End Sub

    ''' <summary>
    ''' Number of data points in series
    ''' </summary>
    <DisplayName("1. Number of Points"), Category("Statistics"), Description("Number of data points in series (read-only)"), [ReadOnly](True)> _
    Public ReadOnly Property NumberOfPoints() As Integer
        Get
            Return _ZedCurve.Points.Count
        End Get
    End Property

    ''' <summary>
    ''' Number of non-calc data points in series
    ''' </summary>
    <DisplayName("2. No. Non-Calc"), Category("Statistics"), Description("Number of data points in series with RCode indicating that it shouldn't be used in calculations (read-only)"), [ReadOnly](True)> _
    Public ReadOnly Property NumberOfNonCalc() As Integer
        Get
            If Table Is Nothing Then Return 0 Else Return Table.NumNonCalc
        End Get
    End Property

    ''' <summary>
    ''' Number of non-detect data points in series
    ''' </summary>
    <DisplayName("3. No. Non-Detect"), Category("Statistics"), Description("Number of non-detect data points in series (read-only)"), [ReadOnly](True)> _
    Public ReadOnly Property NumberOfNonDetect() As Integer
        Get
            If Table Is Nothing Then Return 0 Else Return Table.NumNonDetect
        End Get
    End Property

    ''' <summary>
    ''' Number of data points in series outside of criteria limits
    ''' </summary>
    <DisplayName("4. No. Exceedances"), Category("Statistics"), Description("Number of data points in series exceeding criteria limits for PCode (read-only)"), [ReadOnly](True)> _
    Public ReadOnly Property NumberOfExceedances() As Integer
        Get
            If Table Is Nothing Then Return 0 Else Return Table.NumExceed
        End Get
    End Property

    ''' <summary>
    ''' Arbitrary notes on graph
    ''' </summary>
    <DisplayName("1. Statistics Text"), Category("Statistics Text"), Description("Text containing statistics associated with series (automatically updated)"), Editor("System.ComponentModel.Design.MultilineStringEditor", "System.Drawing.Design.UITypeEditor")> _
    Public Property StatsText() As String
        Get
            Return _StatsText
        End Get
        Set(ByVal value As String)
            _StatsText = value
            If _StatsText <> "" And _StatsVisible Then
                _StatsPosition = enumNotePosition.TopRight
                _StatsVisible = True
            End If
            SetStats()
        End Set
    End Property

    ''' <summary>
    ''' Graph title font (Zed.GraphPane.Title.FontSpec)
    ''' </summary>
    <DisplayName("2. Stats font"), Category("Statistics Text"), Description("Sets the font for the statistics")> _
    Public Property StatsFont() As Font
        Get
            Return _StatsFont
        End Get
        Set(ByVal value As Font)
            _StatsFont = value
            SetStats()
        End Set
    End Property

    ''' <summary>
    ''' Are notes visible? 
    ''' </summary>
    <DisplayName("3. Stats are visible?"), Category("Statistics Text"), Description("Sets the visibility of statistics text")> _
    Public Property StatsVisible() As Boolean
        Get
            Return _StatsVisible
        End Get
        Set(ByVal value As Boolean)
            _StatsVisible = value
            SetStats()
        End Set
    End Property

    ''' <summary>
    ''' Note position
    ''' </summary>
    <DisplayName("4. Stats position"), Category("Statistics Text"), Description("Sets the position of the statistics text if used")> _
    Public Property StatsPosition() As enumNotePosition
        Get
            Return _StatsPosition
        End Get
        Set(ByVal value As enumNotePosition)
            _StatsPosition = value
            SetStats()
        End Set
    End Property

    ''' <summary>
    ''' Note text alignment
    ''' </summary>
    <DisplayName("5. Statistics text alignment"), Category("Statistics Text"), Description("Sets the alignment of the text contained in the statistics text if used")> _
    Public Property StatsAlignment() As enumNoteAlignment
        Get
            Return _StatsAlignment
        End Get
        Set(ByVal value As enumNoteAlignment)
            _StatsAlignment = value
            SetStats()
        End Set
    End Property

    ''' <summary>
    ''' Note border
    ''' </summary>
    <DisplayName("6. Stats border is visible?"), Category("Statistics Text"), Description("If true, a solid border will be drawn around the statistics text (and the background will be filled).")> _
    Public Property StatsBorder() As Boolean
        Get
            Return _StatsBorder
        End Get
        Set(ByVal value As Boolean)
            _StatsBorder = value
            SetStats()
        End Set
    End Property

    Private Sub SetStats()
        'remove any prior object
        For Each obj As ZedGraph.GraphObj In _Zed.GraphPane.GraphObjList
            If TypeOf obj Is ZedGraph.TextObj AndAlso obj.Tag = "Stats:" & _StationID & _PCode Then
                _Zed.GraphPane.GraphObjList.Remove(obj)
                Exit For
            End If
        Next

        If Not _StatsVisible Then Exit Sub

        'if non empty, add a new one
        If _StatsText <> "" And _StatsVisible Then
            Dim obj As New ZedGraph.TextObj()
            With obj
                .Text = _StatsText.Replace("\n", vbCrLf)
                .Tag = "Stats:" & _StationID & _PCode
                .FontSpec = GetFont(_StatsFont, .FontSpec, ActiveGraphTab.GraphOptions.FontScaleFactor)
                .FontSpec.StringAlignment = Choose(_StatsAlignment + 1, StringAlignment.Near, StringAlignment.Center, StringAlignment.Far)
                If _StatsBorder Then
                    .FontSpec.Fill = New ZedGraph.Fill(Color.White)
                    .FontSpec.Border.IsVisible = True
                Else
                    .FontSpec.Fill = New ZedGraph.Fill(Color.Transparent)
                    .FontSpec.Border.IsVisible = False
                End If
                Select Case _StatsPosition
                    Case enumNotePosition.TopLeft
                        .Location = New ZedGraph.Location(0.02, 0.02, ZedGraph.CoordType.ChartFraction, ZedGraph.AlignH.Left, ZedGraph.AlignV.Top)
                    Case enumNotePosition.TopCenter
                        .Location = New ZedGraph.Location(0.5, 0.02, ZedGraph.CoordType.ChartFraction, ZedGraph.AlignH.Center, ZedGraph.AlignV.Top)
                    Case enumNotePosition.TopRight
                        .Location = New ZedGraph.Location(0.98, 0.02, ZedGraph.CoordType.ChartFraction, ZedGraph.AlignH.Right, ZedGraph.AlignV.Top)
                    Case enumNotePosition.BottomLeft
                        .Location = New ZedGraph.Location(0.02, 0.98, ZedGraph.CoordType.ChartFraction, ZedGraph.AlignH.Left, ZedGraph.AlignV.Bottom)
                    Case enumNotePosition.BottomCenter
                        .Location = New ZedGraph.Location(0.5, 0.98, ZedGraph.CoordType.ChartFraction, ZedGraph.AlignH.Center, ZedGraph.AlignV.Bottom)
                    Case enumNotePosition.BottomRight
                        .Location = New ZedGraph.Location(0.98, 0.98, ZedGraph.CoordType.ChartFraction, ZedGraph.AlignH.Right, ZedGraph.AlignV.Bottom)
                End Select
            End With
            _Zed.GraphPane.GraphObjList.Add(obj)
            _Zed.Refresh()
        End If
    End Sub

    ''' <summary>
    ''' X-Axis to which this series is assigned
    ''' </summary>
    <DisplayName("1. X Axis"), Category("Axes"), Description("Sets X-Axis to which this series is assigned")> _
    Public Property XAxis() As enumXAxis
        Get
            Return IIf(_ZedCurve.IsX2Axis, enumXAxis.Top, enumXAxis.Bottom)
        End Get
        Set(ByVal value As enumXAxis)
            _ZedCurve.IsX2Axis = value = enumXAxis.Top
            _ZedCurveMeas.IsX2Axis = _ZedCurve.IsX2Axis
        End Set
    End Property

    ''' <summary>
    ''' Y-Axis to which this series is assigned
    ''' </summary>
    <DisplayName("2. Y Axis"), Category("Axes"), Description("Sets Y-Axis to which this series is assigned")> _
    Public Property YAxis() As enumYAxis
        Get
            Return IIf(_ZedCurve.IsY2Axis, enumYAxis.Right, enumYAxis.Left)
        End Get
        Set(ByVal value As enumYAxis)
            _ZedCurve.IsY2Axis = value = enumYAxis.Right
            _ZedCurve.YAxisIndex = 0
            _ZedCurveMeas.IsY2Axis = value
            _ZedCurveMeas.YAxisIndex = 0
        End Set
    End Property

    ''' <summary>
    ''' Y-Axis index to which this series is assigned
    ''' </summary>
    <DisplayName("3. Y Axis No."), Category("Axes"), Description("Sets Y-Axis number to which this series is assigned (from 1 to 5); you can have multiple axes on the left and/or right Y axis")> _
    Public Property YAxisIndex() As enumYAxisIndex
        Get
            Return _ZedCurve.YAxisIndex
        End Get
        Set(ByVal value As enumYAxisIndex)
            _ZedCurve.YAxisIndex = value
            _ZedCurveMeas.YAxisIndex = value
        End Set
    End Property

    ''' <summary>
    ''' Set color for this series
    ''' </summary>
    <DisplayName("1. Line Color"), Category("Line & Symbol Format"), Description("Sets the color of lines")> _
    Public Property LineColor() As Color
        Get
            Return _ZedCurve.Line.Color
        End Get
        Set(ByVal value As Color)
            If SymbolFillColor = LineColor And SymbolBorderColor = LineColor Then 'reset all to new value--reseting curve color does this automatically
                _ZedCurve.Color = value
                _ZedCurveMeas.Color = value
            Else 'symbol colors had been overridden, so leave as-is (save and reset)
                'Dim fc As Color = SymbolFillColor
                'Dim bc As Color = SymbolBorderColor
                _ZedCurve.Line.Color = value
                _ZedCurveMeas.Line.Color = value
                'SymbolFillColor = fc
                'SymbolBorderColor = bc
            End If
        End Set
    End Property

    ''' <summary>
    ''' Set line type for this series (line is not visible if -1)
    ''' </summary>
    <DisplayName("2. Line Type"), Category("Line & Symbol Format"), Description("Sets the line type")> _
    Public Overridable Property LineType() As enumLineType
        Get
            Return _LineType
        End Get
        Set(ByVal value As enumLineType)
            Dim WasBars As Boolean = _LineType = enumLineType.Bars
            _LineType = value
            With _ZedCurve
                If value = enumLineType.None Then
                    .Line.IsVisible = False
                ElseIf value = enumLineType.Bars Then
                    .Line.Style = Drawing2D.DashStyle.Solid
                    .Line.IsVisible = True
                Else
                    .Line.Style = value
                    .Line.IsVisible = True
                End If
            End With
            _ZedCurveMeas.Line.IsVisible = False
            If _SymbolType = enumSymbolType.Box_Whisker OrElse _LineType = enumLineType.Bars OrElse WasBars Then GetData()
        End Set
    End Property

    ''' <summary>
    ''' Sets the line width (in points)
    ''' </summary>
    <DisplayName("3. Line Thickness"), Category("Line & Symbol Format"), Description("Sets the line width (in points)")> _
    Public Property LineWidth() As Single
        Get
            Return _ZedCurve.Line.Width
        End Get
        Set(ByVal value As Single)
            _ZedCurve.Line.Width = value
        End Set
    End Property

    ''' <summary>
    ''' Is a curve fit through the data points?
    ''' </summary>
    <DisplayName("4. Smooth Curve?"), Category("Line & Symbol Format"), Description("Is a curve fit through the data points?")> _
    Public Property SmoothCurve() As Boolean
        Get
            Return _ZedCurve.Line.IsSmooth
        End Get
        Set(ByVal value As Boolean)
            With _ZedCurve.Line
                If _SymbolType = enumSymbolType.Box_Whisker Then
                    .IsSmooth = False
                Else
                    .IsSmooth = value
                End If
            End With
        End Set
    End Property

    ''' <summary>
    ''' Sets the symbol style
    ''' </summary>
    <DisplayName("5. Symbol Type"), Category("Line & Symbol Format"), Description("Sets the symbol style")> _
    Public Overridable Property SymbolType() As enumSymbolType
        Get
            Return _SymbolType
        End Get
        Set(ByVal value As enumSymbolType)
            Dim WasBW As Boolean = _SymbolType = enumSymbolType.Box_Whisker
            _SymbolType = value
            If ActiveGraphTab.GraphType <> enumGraphType.Profile And _SymbolType = enumSymbolType.Box_Whisker Then _SymbolType = enumSymbolType.Square 'only profile plots can show box/whisker

            With _ZedCurve
                Select Case _SymbolType
                    Case enumSymbolType.None : .Symbol.Type = ZedGraph.SymbolType.None
                    Case enumSymbolType.Square : .Symbol.Type = ZedGraph.SymbolType.Square
                    Case enumSymbolType.Diamond : .Symbol.Type = ZedGraph.SymbolType.Diamond
                    Case enumSymbolType.Triangle : .Symbol.Type = ZedGraph.SymbolType.Triangle
                    Case enumSymbolType.Circle : .Symbol.Type = ZedGraph.SymbolType.Circle
                    Case enumSymbolType.XCross : .Symbol.Type = ZedGraph.SymbolType.XCross
                    Case enumSymbolType.Plus : .Symbol.Type = ZedGraph.SymbolType.Plus
                    Case enumSymbolType.Star : .Symbol.Type = ZedGraph.SymbolType.Star
                    Case enumSymbolType.Tri_Down : .Symbol.Type = ZedGraph.SymbolType.TriangleDown
                    Case enumSymbolType.HDash : .Symbol.Type = ZedGraph.SymbolType.HDash
                    Case enumSymbolType.VDash : .Symbol.Type = ZedGraph.SymbolType.VDash
                    Case enumSymbolType.Box_Whisker : .Symbol.Type = ZedGraph.SymbolType.None
                End Select
                _ZedCurveMeas.Symbol.Type = .Symbol.Type
            End With
            If _SymbolType = enumSymbolType.Box_Whisker Or WasBW Then GetData()
        End Set
    End Property

    ''' <summary>
    ''' Set color for this series
    ''' </summary>
    <DisplayName("6. Symbol Border Color"), Category("Line & Symbol Format"), Description("Sets the border color of symbols (same as line color until overridden)")> _
    Public Property SymbolBorderColor() As Color
        Get
            Return _ZedCurve.Symbol.Border.Color
        End Get
        Set(ByVal value As Color)
            _ZedCurve.Symbol.Border.Color = value
            _ZedCurveMeas.Symbol.Border.Color = value
        End Set
    End Property

    ''' <summary>
    ''' Set color for this series
    ''' </summary>
    <DisplayName("7. Symbol Fill Color"), Category("Line & Symbol Format"), Description("Sets the fill color of symbols (same as line color until overridden)")> _
    Public Property SymbolFillColor() As Color
        Get
            Return _ZedCurve.Symbol.Fill.Color
        End Get
        Set(ByVal value As Color)
            _ZedCurve.Symbol.Fill.Color = value
            _ZedCurveMeas.Symbol.Fill.Color = value
        End Set
    End Property

    ''' <summary>
    ''' Sets the symbol size (in points)
    ''' </summary>
    <DisplayName("8. Symbol Size"), Category("Line & Symbol Format"), Description("Sets the symbol size (in points)")> _
    Public Overridable Property SymbolSize() As Single
        Get
            Return _ZedCurve.Symbol.Size
        End Get
        Set(ByVal value As Single)
            _ZedCurve.Symbol.Size = value
            _ZedCurveMeas.Symbol.Size = value
            If _SymbolType = enumSymbolType.Box_Whisker Then GetData()
        End Set
    End Property

    ''' <summary>
    ''' Is the symbol filled?
    ''' </summary>
    <DisplayName("9. Solid Fill?"), Category("Line & Symbol Format"), Description("Is the symbol filled?")> _
    Public Property SymbolFill() As Boolean
        Get
            Return _ZedCurve.Symbol.Fill.Type = ZedGraph.FillType.Solid
        End Get
        Set(ByVal value As Boolean)
            _ZedCurve.Symbol.Fill.Type = IIf(value, ZedGraph.FillType.Solid, ZedGraph.FillType.None)
            '_ZedCurve.Symbol.Fill.Color = LineColor
            _ZedCurveMeas.Symbol.Fill.Type = IIf(value, ZedGraph.FillType.Solid, ZedGraph.FillType.None)
            '_ZedCurveMeas.Symbol.Fill.Color = LineColor
        End Set
    End Property

End Class

Friend Class clsMoreCriteria
    Friend Property MinDate As Date = Date.MinValue
    Friend Property MaxDate As Date = Date.MaxValue
    Friend Property MinDepth As Double = Double.MinValue
    Friend Property MaxDepth As Double = Double.MaxValue
    Friend Property MinWidth As Double = Double.MinValue
    Friend Property MaxWidth As Double = Double.MaxValue
    Friend Property lstCCodes As New Generic.List(Of String)
    Friend Property lstLCodes As New Generic.List(Of String)
    Friend Property lstSCodes As New Generic.List(Of String)
    Friend Property lstQCodes As New Generic.List(Of String)
    Public Overrides Function ToString() As String
        Dim s As String = ""
        If MinDate > New Date(1800, 1, 1) Or MaxDate < New Date(3000, 1, 1) Then s &= IIf(s = "", "", "; ") & String.Format("Dates: {0:MM/dd/yyyy}-{1:MM/dd/yyyy}", MinDate, MaxDate)
        If (MinDepth <> Double.MinValue And MinDepth <> -9999) Or (MaxDepth <> Double.MaxValue And MaxDepth <> 9999) Then s &= IIf(s = "", "", "; ") & String.Format("Depths: {0}-{1}", Math.Max(-9999, MinDepth), Math.Min(9999, MaxDepth))
        If (MinWidth <> Double.MinValue And MinWidth <> -9999) Or (MaxWidth <> Double.MaxValue And MaxWidth <> 9999) Then s &= IIf(s = "", "", "; ") & String.Format("Widths: {0}-{1}", Math.Max(-9999, MinWidth), Math.Min(9999, MaxWidth))
        For i As Integer = 1 To 4
            Dim lst As Generic.List(Of String) = Choose(i, lstCCodes, lstLCodes, lstSCodes, lstQCodes)
            Dim cod As String = Choose(i, "CCodes", "LCodes", "SCodes", "QCodes")
            Dim csv As String = ""
            For Each item As String In lst
                csv &= IIf(csv = "", "", ",") & item
            Next
            If lst.Count > 0 Then s &= IIf(s = "", "", "; ") & String.Format("{0}: {1}", cod, csv)
        Next
        If s = "" Then s = "None"
        Return s
    End Function
End Class

''' <summary>
''' This class was created to display time series information in the properties grid
''' </summary>
Friend Class clsSeriesTimeSeries : Inherits clsSeries

    Friend Sub New(ByRef Zed As ZedGraph.ZedGraphControl)
        MyBase.New(Zed, enumGraphType.TimeSeries)
    End Sub

    Friend Overrides Sub GetDefaultLabel()
        If _Table.TableType = clsTable.enumTableType.Calibration AndAlso Not _Table.Calibration.AutoSeriesPrefix Then
            Label = _Table.Calibration.SeriesPrefix 'will append suffix elsewhere
        Else
            Label = String.Format("{0} at {1}", _PCode, _StationID)
        End If
    End Sub

    Public Overrides Property Label As String
        Get
            If _Table IsNot Nothing AndAlso _Table.TableType = clsTable.enumTableType.Calibration Then
                Return MyBase.Label.Replace(_Table.Calibration.LegendSuffixSimulated, "").Trim
            Else
                Return MyBase.Label
            End If
        End Get
        Set(value As String)
            If _Table IsNot Nothing AndAlso _Table.TableType = clsTable.enumTableType.Calibration Then
                _ZedCurve.Label.Text = IIf(value = "", "", value & " ") & _Table.Calibration.LegendSuffixSimulated
                _ZedCurveMeas.Label.Text = IIf(value = "", "", value & " ") & _Table.Calibration.LegendSuffixMeasured
            Else
                MyBase.Label = value
            End If
        End Set
    End Property

    Public Overrides Property Table As clsTable
        Get
            Return MyBase.Table
        End Get
        Set(value As clsTable)
            MyBase.Table = value
            If value IsNot Nothing AndAlso _Table.TableType = clsTable.enumTableType.Calibration Then _Zed.GraphPane.CurveList.Insert(1, _ZedCurveMeas)
        End Set
    End Property

    Public Overrides Property SymbolType As clsSeries.enumSymbolType
        Get
            Return MyBase.SymbolType
        End Get
        Set(value As clsSeries.enumSymbolType)
            MyBase.SymbolType = value
            If _Table IsNot Nothing AndAlso _Table.TableType = clsTable.enumTableType.Calibration Then _ZedCurve.Symbol.IsVisible = False 'force symbols to be hidden for simulated
        End Set
    End Property

    ''' <summary>
    ''' Retrieve data into curve given the current station id and pcode; also computes any curve fits that are requested
    ''' </summary>
    Friend Overrides Sub GetData()
        Try
            If _IsCalc Then Exit Sub
            MyBase.GetData()

            If _StationID <> "" And _PCode <> "" Then

                Dim ScaleType As clsAxis.enumScaleType = clsAxis.AxisExtra(_Zed.GraphPane.XAxis.Tag).ScaleType

                If _Table.TableType = clsTable.enumTableType.Calibration Then
                    With _Table.Calibration
                        Dim dtMeas As DataTable = .GetData(_StationID, _PCode, clsCalibration.enumDataType.Measured)
                        Dim dtSim As DataTable = .GetData(_StationID, _PCode, clsCalibration.enumDataType.Simulated)
                        _ZedCurve.Clear()
                        If dtSim IsNot Nothing Then
                            For Each r As DataRow In dtSim.Rows
                                Dim dt As Date = r(0)
                                If ScaleType = clsAxis.enumScaleType.Date_Julian Then 'Reset all years to 1900 (formatting will hide it)
                                    dt = New Date(1900, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second)
                                End If
                                _ZedCurve.AddPoint(dt.ToOADate, r(1))
                            Next
                        End If
                        _ZedCurveMeas.Clear()
                        If dtMeas IsNot Nothing Then
                            For Each r As DataRow In dtMeas.Rows
                                Dim dt As Date = r(0)
                                If ScaleType = clsAxis.enumScaleType.Date_Julian Then 'Reset all years to 1900 (formatting will hide it)
                                    dt = New Date(1900, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second)
                                End If
                                _ZedCurveMeas.AddPoint(dt.ToOADate, r(1))
                            Next
                        End If

                        Dim Stats() As clsCalibration.structStats = Nothing, NumPairs As Integer, R2 As Double
                        .CalcStats(dtMeas, dtSim, Stats, NumPairs, R2)
                        _StatsText = String.Format("Station: {0}\nPCode: {1}\nMin Date: {2}\nMax Date: {3}\n" &
                                                   "----{4} Data----\nCount: {5}\nMean Value: {6:N3}\n" &
                                                   "{7} %tile: {8:N3}\n{9} %tile: {10:N3}\n" &
                                                   "----{11} Data----\nCount: {12}\nMean Value: {13:N3}\n" &
                                                   "{14} %tile: {15:N3}\n{16} %tile: {17:N3}",
                                                   _StationID, _PCode, _Table.Calibration.MinDate.ToString(MyDateTimeFormat), _Table.Calibration.MaxDate.ToString(MyDateTimeFormat),
                                                   _Table.Calibration.LegendSuffixSimulated, Stats(1).Count, Stats(1).Mean,
                                                   .PercentileStringLo, .PercentileLo(Stats(1).Percentiles), .PercentileStringHi, .PercentileHi(Stats(1).Percentiles),
                                                   _Table.Calibration.LegendSuffixMeasured, Stats(0).Count, Stats(0).Mean,
                                                   .PercentileStringLo, .PercentileLo(Stats(0).Percentiles), .PercentileStringHi, .PercentileHi(Stats(0).Percentiles))
                    End With
                Else
                    With _ZedCurve
                        .Clear()
                        Dim dtMeas As DataTable
                        With MoreCriteria
                            If MoreCriteria IsNot Nothing AndAlso MoreCriteria.ToString <> "None" Then
                                dtMeas = _Table.GetResults(_StationID, _PCode, .MinDate, .MaxDate, .MinDepth, .MaxDepth, .MinWidth, .MaxWidth, .lstCCodes, .lstLCodes, .lstSCodes, .lstQCodes)
                            Else
                                dtMeas = _Table.GetResults(_StationID, _PCode)
                            End If
                        End With
                        If dtMeas Is Nothing OrElse dtMeas.Rows.Count = 0 Then Exit Sub
                        'in some circumstances (Paradox), data may not be sorted by date; do so now
                        dtMeas.DefaultView.Sort = "Date_Time"
                        'if plotting julian date, need base year to assign, but make sure is leap year
                        Dim baseyear As Integer = CDate(dtMeas.DefaultView.Item(0)(0)).Year
                        While Not Date.IsLeapYear(baseyear)
                            baseyear -= 1
                        End While
                        Dim sum As Single = 0, min As Single = Single.MaxValue, max As Single = Single.MinValue
                        For r As Integer = 0 To dtMeas.DefaultView.Count - 1
                            Dim dt As Date = dtMeas.DefaultView.Item(r)(0)
                            Dim conc As Double = dtMeas.DefaultView.Item(r)(1)
                            If LineType = enumLineType.Bars And ScaleType <> clsAxis.enumScaleType.Date_Julian Then
                                'assume value is at end-of period, so add bar by drawing to zero at previous time, then up to next value, then over to next value
                                Dim dtPrev As Date
                                If r = 0 Then
                                    Dim deltat As TimeSpan = TimeSpan.Zero
                                    If dtMeas.Rows.Count > 1 Then deltat = dtMeas.DefaultView.Item(1)(0).Subtract(dt) 'get deltat from next time step
                                    dtPrev = dt.Add(-deltat)
                                Else
                                    dtPrev = dtMeas.DefaultView.Item(r - 1)(0)
                                End If
                                _ZedCurve.AddPoint(dtPrev.ToOADate, 0)
                                _ZedCurve.AddPoint(dtPrev.ToOADate, conc)
                                _ZedCurve.AddPoint(dt.ToOADate, conc)
                                _ZedCurve.AddPoint(dt.ToOADate, 0.0)
                            Else
                                If ScaleType = clsAxis.enumScaleType.Date_Julian Then 'Reset all years to base year (formatting will hide it)
                                    dt = New Date(baseyear, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second)
                                End If
                                _ZedCurve.AddPoint(dt.ToOADate, conc)
                            End If
                            sum += conc
                            min = Math.Min(min, conc)
                            max = Math.Max(max, conc)
                        Next
                        Dim mindate, maxdate As Date
                        If .NPts = 0 Then
                            min = 0 : max = 0 : mindate = Now : maxdate = Now
                        Else
                            mindate = Date.FromOADate(.Points(0).X)
                            maxdate = Date.FromOADate(.Points(.NPts - 1).X)
                        End If
                        _StatsText = String.Format("Station: {0}\nPCode: {1}\nCount: {2}\nNon-Calc: {3}\nNon-Det: {4}\nExceed: {5} ({11:N1}%)\n" &
                                                       "Min Date: {6}\nMax Date: {7}\nMin Value: {8:N3}\nMax Value: {9:N3}\nMean Value: {10:N3}",
                                                       _StationID, _PCode, .NPts, Table.NumNonCalc, Table.NumNonDetect, Table.NumExceed,
                                                       mindate.ToString(MyDateTimeFormat), maxdate.ToString(MyDateTimeFormat), min, max, sum / Math.Max(.NPts, 1), Table.NumExceed * 100.0 / Math.Max(.NPts, 1))
                    End With
                End If

            End If
            _NeedsReload = False
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

End Class

''' <summary>
''' This class was created to display longitudinal profile information in the properties grid
''' </summary>
Friend Class clsSeriesLongProfile : Inherits clsSeries

    Private _ProfXPlot As clsGraphTab.enumProfileXPlot

    Friend Sub New(ByRef Zed As ZedGraph.ZedGraphControl)
        MyBase.New(Zed, enumGraphType.Profile)
        SymbolFill = True
    End Sub

    ''' <summary>
    ''' Indicates the x-axis value that should be used (should be same for all series)
    ''' </summary>
    <DisplayName("2a. Profile Option"), Category("(Data)"), Description("Controls how values are plotted on the X Axis")> _
    Public Property ProfXPlot() As clsGraphTab.enumProfileXPlot
        Get
            Return _ProfXPlot
        End Get
        Set(ByVal value As clsGraphTab.enumProfileXPlot)
            _ProfXPlot = value
            GetData()

            'reset x axis label
            With clsAxis.AxisExtra(_Zed.GraphPane.XAxis.Tag)
                If .AutoTitle Then _Zed.GraphPane.XAxis.Title.Text = IIf(_ProfXPlot = clsGraphTab.enumProfileXPlot.RiverMile, "River Mile", "Segment Number")
            End With
        End Set
    End Property

    Friend Overrides Sub GetDefaultLabel()
        Label = String.Format("{0} at {1}", _PCode, _DateTime.ToString(MyDateTimeFormat))
    End Sub

    Public Overrides Property Label As String
        Get
            If _Table IsNot Nothing AndAlso _Table.TableType = clsTable.enumTableType.Calibration Then
                Return MyBase.Label.Replace(_Table.Calibration.LegendSuffixSimulated, "").Trim
            Else
                Return MyBase.Label
            End If
        End Get
        Set(value As String)
            If _Table IsNot Nothing AndAlso _Table.TableType = clsTable.enumTableType.Calibration Then
                _ZedCurve.Label.Text = IIf(value = "", "", value & " ") & _Table.Calibration.LegendSuffixSimulated
                _ZedCurveMeas.Label.Text = IIf(value = "", "", value & " ") & _Table.Calibration.LegendSuffixMeasured
            Else
                MyBase.Label = value
            End If
        End Set
    End Property

    ''' <summary>
    ''' Retrieve data into curve given the current pcode; also computes any curve fits that are requested
    ''' </summary>
    Friend Overrides Sub GetData()
        If _Table.TableType = clsTable.enumTableType.Calibration Then Exit Sub
        MyBase.GetData()
        If _PCode <> "" Then

            _ZedCurve.Clear()

            Dim sum As Single = 0, min As Single = Single.MaxValue, max As Single = Single.MinValue, minRMile As Single = Single.MaxValue, maxRMile As Single = Single.MinValue
            Dim mindate As Date = _DateTime.AddMinutes(-_TimeWindowMin(_TimeWindow) / 2)
            Dim maxdate As Date = _DateTime.AddMinutes(_TimeWindowMin(_TimeWindow) / 2)

            If _Table.TableType = clsTable.enumTableType.BMD Then
                Dim arDates() As Date = _Table.dsBMD.Times
                If _DateTime < arDates(0) Or _DateTime > arDates(arDates.Length - 1) Then Exit Sub
                Dim y As Single() = _Table.dsBMD.Conc(_PCode, _DateTime)
                For i As Integer = 0 To _Table.dsBMD.NumSegments - 1
                    Dim Station As String = _Table.dsBMD.SegmentName(i)
                    If _Table.StationSupport.ContainsKey(Station) Then
                        If _StationID = "All Branches" OrElse (_StationID = _Table.StationSupport(Station).BranchID AndAlso _Table.StationSupport(Station).RiverMile >= 0) Then
                            If y(i) <> Double.NaN Then
                                _ZedCurve.AddPoint(IIf(_ProfXPlot = clsGraphTab.enumProfileXPlot.RiverMile, _Table.StationSupport(Station).RiverMile, i + 1), y(i))
                                sum += y(i)
                                min = Math.Min(min, y(i))
                                max = Math.Max(max, y(i))
                                minRMile = Math.Min(minRMile, _Table.StationSupport(Station).RiverMile)
                                maxRMile = Math.Max(maxRMile, _Table.StationSupport(Station).RiverMile)
                            End If
                        End If
                    End If
                Next
            ElseIf _Table.TableType = clsTable.enumTableType.WRDBData Then
                'cannot display profile data
            Else
                If _ProfXPlot = clsGraphTab.enumProfileXPlot.RiverMile Then 'note: will not display non-BMD series if plotversus segment number
                    Dim dr As DbDataReader = _Table.WRDBProject.DB.ExecuteReader("Select Station_ID,RCode,Result from {0} where PCode='{1}' and Date_Time Between {2} and {3}", _Table.WRDBProject.DB.AddBrackets(_Table.TableName), _PCode, _Table.WRDBProject.DB.DateTime(mindate), _Table.WRDBProject.DB.DateTime(maxdate))
                    If dr Is Nothing Then Exit Sub
                    With dr
                        'assemble list of all points at each river mile and plot box & whisker series
                        Dim dictData As New Generic.SortedDictionary(Of Double, Generic.List(Of Double))
                        While .Read
                            Dim Station As String = .GetString(0)
                            If _Table.StationSupport.ContainsKey(Station) Then
                                If _Table.StationSupport(Station).RiverMile >= 0 And (_StationID = "All Branches" Or _StationID = _Table.StationSupport(Station).BranchID) Then
                                    Dim RMile As Double = _Table.StationSupport(Station).RiverMile
                                    Dim y As Double
                                    If Table.GetResult("", _PCode, _DateTime, .GetValue(1), .GetValue(2), y) Then
                                        If SymbolType = enumSymbolType.Box_Whisker Then
                                            If dictData.ContainsKey(RMile) Then
                                                dictData(RMile).Add(y)
                                            Else
                                                dictData.Add(RMile, New Generic.List(Of Double))
                                            End If
                                        Else
                                            _ZedCurve.AddPoint(RMile, y)
                                        End If
                                        sum += y
                                        min = Math.Min(min, y)
                                        max = Math.Max(max, y)
                                        minRMile = Math.Min(minRMile, RMile)
                                        maxRMile = Math.Max(maxRMile, RMile)
                                    End If
                                End If
                            End If
                        End While
                        .Close()
                        dr = Nothing
                        If _SymbolType = enumSymbolType.Box_Whisker Then
                            For Each kv As Generic.KeyValuePair(Of Double, Generic.List(Of Double)) In dictData
                                If kv.Value.Count > 0 Then
                                    Dim RMile As Double = kv.Key
                                    Dim Median, Q25, Q75, Mn, Mx As Double
                                    CalcStats(kv.Value, Median, Q25, Q75, Mn, Mx)
                                    Dim Width As Double = (_Zed.GraphPane.XAxis.Scale.Max - _Zed.GraphPane.XAxis.Scale.Min) * SymbolSize / 1000
                                    If _LineType <> enumLineType.None Then _ZedCurve.AddPoint(RMile, Median)
                                    With _ZedCurve
                                        .Line.IsVisible = True
                                        AddBWSymbol(_ZedCurve, RMile, Width, Median, Q25, Q75, Mn, Mx)
                                        If _LineType <> enumLineType.None Then _ZedCurve.AddPoint(RMile, Median)
                                    End With
                                End If
                            Next
                        End If
                    End With
                End If
                With _ZedCurve
                    If .NPts = 0 Then minRMile = 0 : maxRMile = 0 : min = 0 : max = 0
                    _StatsText = String.Format("Branch: {0}\nPCode: {1}\nDate: {2}\nCount: {3}\nNon-Calc: {4}\nNon-Det: {5}\nExceed: {6} ({12:N1}%)\nMin RMile: {7:N2}\nMax RMile: {8:N2}\nMin Value: {9:N3}\nMax Value: {10:N3}\nMean Value: {11:N3}", _StationID, _PCode, _DateTime.ToString(MyDateTimeFormat), .NPts, Table.NumNonCalc, Table.NumNonDetect, Table.NumExceed, minRMile, maxRMile, min, max, sum / Math.Max(.NPts, 1), Table.NumExceed * 100.0 / Math.Max(.NPts, 1))
                End With
            End If
        End If
        _NeedsReload = False
    End Sub

    ''' <summary>
    ''' Given a list of numbers, return the statistics needed for box and whisker
    ''' </summary>
    Private Sub CalcStats(ByVal lst As Generic.List(Of Double), ByRef Median As Double, ByRef Q25 As Double, ByRef Q75 As Double, ByRef Min As Double, ByRef Max As Double)
        lst.Sort()

        If lst.Count = 0 Then
            Throw New IndexOutOfRangeException("An empty list was passed to CalcMedian.")
        ElseIf lst.Count Mod 2 = 0 Then 'is even; return avg of center two values
            Median = (lst(lst.Count / 2 - 1) + lst(lst.Count / 2)) / 2.0
        Else
            Median = lst((lst.Count - 1) / 2)
        End If

        Min = lst(0)
        Max = lst(lst.Count - 1)

        If lst.Count = 1 Then
            Q25 = lst(0)
            Q75 = lst(0)
        Else
            If lst.Count Mod 4 = 0 Then 'get exact value in list
                Q25 = lst((lst.Count - 1) / 4)
                Q75 = lst((lst.Count - 1) * 3 / 4)
            Else
                Dim p1, mult As Double, i1, i2 As Integer
                p1 = (lst.Count - 1) / 4.0
                i1 = Math.Floor(p1)
                i2 = Math.Ceiling(p1)
                mult = p1 - i1
                Q25 = lst(i1) + (lst(i2) - lst(i1)) * mult
                p1 = (lst.Count - 1) * 3.0 / 4.0
                i1 = Math.Floor(p1)
                i2 = Math.Ceiling(p1)
                mult = p1 - i1
                Q75 = lst(i1) + (lst(i2) - lst(i1)) * mult
            End If
        End If
    End Sub

    ''' <summary>
    ''' Add Box and Whisker symbol
    ''' </summary>
    Private Sub AddBWSymbol(ByRef crv As ZedGraph.LineItem, ByVal RMile As Double, ByVal Width As Double, ByVal Median As Double, ByVal Q25 As Double, ByVal Q75 As Double, ByVal Min As Double, ByVal Max As Double)
        With crv
            Dim x1 As Double = RMile - Width / 2
            Dim x2 As Double = RMile
            Dim x3 As Double = RMile + Width / 2
            .AddPoint(Double.MaxValue, Double.MaxValue) 'missing point
            .AddPoint(x1, Min)
            .AddPoint(x3, Min)
            .AddPoint(x2, Min)
            .AddPoint(x2, Q25)
            .AddPoint(x1, Q25)
            .AddPoint(x1, Q75)
            .AddPoint(x3, Q75)
            .AddPoint(x3, Q25)
            .AddPoint(x1, Q25)
            .AddPoint(x1, Median)
            .AddPoint(x3, Median)
            .AddPoint(x3, Q75)
            .AddPoint(x2, Q75)
            .AddPoint(x2, Max)
            .AddPoint(x1, Max)
            .AddPoint(x3, Max)
            .AddPoint(Double.MaxValue, Double.MaxValue)
            .AddPoint(x2, Median) 'position here in case want to draw line
        End With
    End Sub

    ''' <summary>
    ''' Compute profile curve which is locus of points of average values of constituent at each river mile
    ''' </summary>
    ''' <param name="crv">Newly created curve</param>
    Private Sub ComputeProfile(ByRef crv As ZedGraph.LineItem)
        Try
            With _ZedCurve
                Dim colSum As New Generic.SortedDictionary(Of Double, Double)
                Dim colNum As New Generic.SortedDictionary(Of Double, Integer)
                For i As Integer = 0 To .NPts - 1
                    With .Points(i)
                        If Not colSum.ContainsKey(.X) Then
                            colSum.Add(.X, 0)
                            colNum.Add(.X, 0)
                        End If
                        colSum.Item(.X) += .Y
                        colNum.Item(.X) += 1
                    End With
                    Application.DoEvents()
                Next i
                For Each rmile As Double In colSum.Keys
                    crv.AddPoint(rmile, colSum(rmile) / colNum(rmile))
                Next
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

End Class

''' <summary>
''' This class was created to display depth profile information in the properties grid
''' </summary>
Friend Class clsSeriesDepthProfile : Inherits clsSeries

    Friend Sub New(ByRef Zed As ZedGraph.ZedGraphControl)
        MyBase.New(Zed, enumGraphType.Depth)
        SymbolFill = True
        SymbolType = enumSymbolType.Circle
        LineType = enumLineType.None
    End Sub

    Friend Overrides Sub GetDefaultLabel()
        Label = String.Format("{0} at {1}", _StationID, _DateTime.ToString(MyDateTimeFormat))
    End Sub

    ''' <summary>
    ''' Retrieve data into curve given the current pcode; also computes any curve fits that are requested
    ''' </summary>
    Friend Overrides Sub GetData()
        If _Table.TableType = clsTable.enumTableType.Calibration Then Exit Sub
        MyBase.GetData()
        If _PCode <> "" Then
            With _ZedCurve
                .Clear()

                'cannot plot BMD or LSPC data
                Select Case _Table.TableType
                    Case clsTable.enumTableType.BMD, clsTable.enumTableType.WRDBData
                        Exit Sub
                End Select

                Dim sum As Single = 0, min As Single = Single.MaxValue, max As Single = Single.MinValue, minDepth As Single = Single.MaxValue, maxDepth As Single = Single.MinValue
                Dim mindate As Date = _DateTime.AddMinutes(-_TimeWindowMin(_TimeWindow) / 2).AddMinutes(-1)
                Dim maxdate As Date = _DateTime.AddMinutes(_TimeWindowMin(_TimeWindow) / 2).AddMinutes(1)
                Dim dr As DbDataReader = _Table.WRDBProject.DB.ExecuteReader("SELECT Depth_M,RCode,Result from {0} WHERE Station_ID='{1}' AND PCode='{2}' AND Depth_M IS NOT NULL AND Date_Time BETWEEN {3} and {4} ORDER BY Depth_M", _Table.WRDBProject.DB.AddBrackets(_Table.TableName), _StationID, _PCode, _Table.WRDBProject.DB.DateTime(mindate), _Table.WRDBProject.DB.DateTime(maxdate))
                If dr Is Nothing Then Exit Sub
                While dr.Read
                    Dim Depth As Double = dr.GetValue(0)
                    Dim Result As Double
                    If Table.GetResult(_StationID, _PCode, _DateTime, dr.GetValue(1), dr.GetValue(2), Result) Then
                        .AddPoint(Result, Depth)
                        sum += Result
                        min = Math.Min(min, Result)
                        max = Math.Max(max, Result)
                        minDepth = Math.Min(minDepth, Depth)
                        maxDepth = Math.Max(maxDepth, Depth)
                    End If
                    Application.DoEvents()
                End While
                dr.Close()
                dr = Nothing
                If .NPts = 0 Then minDepth = 0 : maxDepth = 0 : min = 0 : max = 0
                _StatsText = String.Format("Station: {0}\nPCode: {1}\nDate: {2}\nCount: {3}\nNon-Calc: {4}\nNon-Det: {5}\nExceed: {6} ({12:N1}%)\nMin Depth: {7:N2}\nMax Depth: {8:N2}\nMin Value: {9:N3}\nMax Value: {10:N3}\nMean Value: {11:N3}", _StationID, _PCode, _DateTime.ToString(MyDateTimeFormat), .NPts, Table.NumNonCalc, Table.NumNonDetect, Table.NumExceed, minDepth, maxDepth, min, max, sum / Math.Max(.NPts, 1), Table.NumExceed * 100.0 / Math.Max(.NPts, 1))
            End With
        End If
        _NeedsReload = False
    End Sub

    ''' <summary>
    ''' Compute profile curve which is locus of points of average values of constituent at each depth
    ''' </summary>
    ''' <param name="crv">Newly created curve</param>
    Private Sub ComputeProfile(ByRef crv As ZedGraph.LineItem)
        Try
            With _ZedCurve
                Dim colSum As New Generic.SortedDictionary(Of Double, Double)
                Dim colNum As New Generic.SortedDictionary(Of Double, Integer)
                For i As Integer = 0 To .NPts - 1
                    With .Points(i)
                        If Not colSum.ContainsKey(.Y) Then
                            colSum.Add(.Y, 0)
                            colNum.Add(.Y, 0)
                        End If
                        colSum.Item(.Y) += .X
                        colNum.Item(.Y) += 1
                    End With
                    Application.DoEvents()
                Next i
                For Each depth As Double In colSum.Keys
                    crv.AddPoint(colSum(depth) / colNum(depth), depth)
                Next
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

End Class

''' <summary>
''' This class was created to display width profile information in the properties grid
''' </summary>
''' <remarks>Note: only Public properties are visible in the Properties grid</remarks>
<DefaultProperty("StationID")> _
Friend Class clsSeriesWidthProfile : Inherits clsSeries

    Friend Sub New(ByRef Zed As ZedGraph.ZedGraphControl)
        MyBase.New(Zed, enumGraphType.Width)
        SymbolFill = True
    End Sub

    Friend Overrides Sub GetDefaultLabel()
        Label = String.Format("{0} at {1}", _StationID, _DateTime.ToString(MyDateTimeFormat))
    End Sub

    ''' <summary>
    ''' Only allow "symbols" format
    ''' </summary>
    Public Overrides Property LineType() As enumLineType
        Get
            Return MyBase.LineType
        End Get
        Set(ByVal value As enumLineType)
            If IsCalc Then MyBase.LineType = value Else MyBase.LineType = enumLineType.None
        End Set
    End Property

    ''' <summary>
    ''' Sets the symbol style (do not allow NONE)
    ''' </summary>
    Public Overrides Property SymbolType() As enumSymbolType
        Get
            Return MyBase.SymbolType
        End Get
        Set(ByVal value As enumSymbolType)
            If value = enumSymbolType.None And Not IsCalc Then
                MyBase.SymbolType = enumSymbolType.Circle
            Else
                MyBase.SymbolType = value
            End If
        End Set
    End Property

    ''' <summary>
    ''' Retrieve data into curve given the current pcode
    ''' </summary>
    Friend Overrides Sub GetData()
        If _Table.TableType = clsTable.enumTableType.Calibration Then Exit Sub
        MyBase.GetData()
        If _PCode <> "" Then
            With ZedCurve
                .Clear()

                'cannot plot BMD or LSPC data
                Select Case _Table.TableType
                    Case clsTable.enumTableType.BMD, clsTable.enumTableType.WRDBData
                        Exit Sub
                End Select

                Dim sum As Single = 0, min As Single = Single.MaxValue, max As Single = Single.MinValue, minWidth As Single = Single.MaxValue, maxWidth As Single = Single.MinValue
                Dim mindate As Date = _DateTime.AddMinutes(-_TimeWindowMin(_TimeWindow) / 2)
                Dim maxdate As Date = _DateTime.AddMinutes(_TimeWindowMin(_TimeWindow) / 2)
                Dim dr As DbDataReader = _Table.WRDBProject.DB.ExecuteReader("SELECT LEW_Pct,RCode,Result FROM {0} WHERE Station_ID='{1}' AND PCode='{2}' AND LEW_Pct IS NOT NULL AND Date_Time BETWEEN {3} and {4} ORDER BY LEW_Pct", _Table.WRDBProject.DB.AddBrackets(_Table.TableName), _StationID, _PCode, _Table.WRDBProject.DB.DateTime(mindate), _Table.WRDBProject.DB.DateTime(maxdate))
                If dr Is Nothing Then Exit Sub
                While dr.Read
                    Dim Width As Double = dr.GetValue(0)
                    Dim Result As Double
                    If Table.GetResult(_StationID, _PCode, _DateTime, dr.GetValue(1), dr.GetValue(2), Result) Then
                        .AddPoint(Width, Result)
                        sum += Result
                        min = Math.Min(min, Result)
                        max = Math.Max(max, Result)
                        minWidth = Math.Min(minWidth, Width)
                        maxWidth = Math.Max(maxWidth, Width)
                    End If
                    Application.DoEvents()
                End While
                dr.Close()
                dr = Nothing
                If .NPts = 0 Then minWidth = 0 : maxWidth = 0 : min = 0 : max = 0
                _StatsText = String.Format("Station: {0}\nPCode: {1}\nDate: {2}\nCount: {3}\nNon-Calc: {4}\nNon-Det: {5}\nExceed: {6} ({12:N1}%)\nMin Width: {7:N2}\nMax Width: {8:N2}\nMin Value: {9:N3}\nMax Value: {10:N3}\nMean Value: {11:N3}", _StationID, _PCode, _DateTime.ToString(MyDateTimeFormat), .NPts, Table.NumNonCalc, Table.NumNonDetect, Table.NumExceed, minWidth, maxWidth, min, max, sum / Math.Max(.NPts, 1), Table.NumExceed * 100.0 / Math.Max(.NPts, 1))
            End With
        End If
        _NeedsReload = False
    End Sub

    ''' <summary>
    ''' Compute profile curve which is locus of points of average values of constituent at each width
    ''' </summary>
    ''' <param name="crv">Newly created curve</param>
    Private Sub ComputeProfile(ByRef crv As ZedGraph.LineItem)
        Try
            With ZedCurve
                Dim colSum As New Generic.SortedDictionary(Of Double, Double)
                Dim colNum As New Generic.SortedDictionary(Of Double, Integer)
                For i As Integer = 0 To .NPts - 1
                    With .Points(i)
                        If Not colSum.ContainsKey(.X) Then
                            colSum.Add(.X, 0)
                            colNum.Add(.X, 0)
                        End If
                        colSum.Item(.X) += .Y
                        colNum.Item(.X) += 1
                    End With
                    Application.DoEvents()
                Next i
                For Each width As Double In colSum.Keys
                    crv.AddPoint(width, colSum(width) / colNum(width))
                Next
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

End Class

''' <summary>
''' This class was created to display scatter plot information in the properties grid
''' </summary>
''' <remarks>Note: only Public properties are visible in the Properties grid</remarks>
Friend Class clsSeriesScatter : Inherits clsSeries

    Private _StationIDX, _PCodeX, _PCodeListX As String

    ''' <summary>
    ''' List of valid PCodes associated with series (semi-colon separated)
    ''' </summary>
    Friend Property PCodeListX() As String
        Get
            Return _PCodeListX
        End Get
        Set(ByVal value As String)
            _PCodeListX = value
        End Set
    End Property

    Friend Sub New(ByRef Zed As ZedGraph.ZedGraphControl)
        MyBase.New(Zed, enumGraphType.Scatter)
        LineType = enumLineType.None
        SymbolType = enumSymbolType.Circle
    End Sub

    Friend Overrides Sub GetDefaultLabel()
        If _Table.TableType = clsTable.enumTableType.Calibration Then
            Label = String.Format("{0} vs. {1}", _Table.Calibration.LegendSuffixSimulated, _Table.Calibration.LegendSuffixMeasured)
        Else
            If _StationID = _StationIDX Then
                Label = _StationID
            Else
                Label = String.Format("{0} vs {1}", _StationID, _StationIDX)
            End If
        End If
    End Sub

    ''' <summary>
    ''' Station ID associated with x axis
    ''' </summary>
    <DisplayName("5a. Station ID (X-Axis)"), Category("(Data)"), Description("Station ID plotted on X-Axis (read-only)"), [ReadOnly](True)> _
    Public Property StationIDX() As String
        Get
            Return _StationIDX
        End Get
        Set(ByVal value As String)
            _StationIDX = value
            _NeedsReload = True
        End Set
    End Property

    ''' <summary>
    ''' PCode associated with x axis
    ''' </summary>
    <DisplayName("6a. PCode (X-Axis)"), Category("(Data)"), Description("PCode plotted on X-Axis (read-only)"), [ReadOnly](True)> _
    Public Property PCodeX() As String
        Get
            Return _PCodeX
        End Get
        Set(ByVal value As String)
            _PCodeX = value
            _NeedsReload = True
        End Set
    End Property

    ''' <summary>
    ''' Station ID associated with y axis
    ''' </summary>
    Friend Property StationIDY() As String
        Get
            Return _StationID
        End Get
        Set(ByVal value As String)
            _StationID = value
            _NeedsReload = True
        End Set
    End Property

    ''' <summary>
    ''' PCode associated with y axis
    ''' </summary>
    Friend Property PCodeY() As String
        Get
            Return _PCode
        End Get
        Set(ByVal value As String)
            _PCode = value
            _NeedsReload = True
        End Set
    End Property

    ''' <summary>
    ''' Only allow "symbols" format
    ''' </summary>
    Public Overrides Property LineType() As enumLineType
        Get
            If IsCalc Then Return MyBase.LineType Else Return enumLineType.None
        End Get
        Set(ByVal value As enumLineType)
            If IsCalc Then MyBase.LineType = value Else MyBase.LineType = enumLineType.None
        End Set
    End Property

    ''' <summary>
    ''' Only allow "symbols" format
    ''' </summary>
    Public Overrides Property SymbolType() As enumSymbolType
        Get
            Return MyBase.SymbolType
        End Get
        Set(ByVal value As enumSymbolType)
            If value = enumSymbolType.None And Not IsCalc Then
                MyBase.SymbolType = enumSymbolType.Circle
            Else
                MyBase.SymbolType = value
            End If
        End Set
    End Property

    ''' <summary>
    ''' Retrieve data into curve given the current station id and pcode; also computes any curve fits that are requested
    ''' </summary>
    Friend Overrides Sub GetData()
        If _Table Is Nothing Then Exit Sub

        MyBase.GetData()

        If _StationIDX <> "" And _PCodeX <> "" And _StationID <> "" And _PCode <> "" Then
            Dim xsum As Single = 0.0, xmin As Double = Double.MaxValue, xmax As Double = Double.MinValue
            Dim ysum As Single = 0.0, ymin As Double = Double.MaxValue, ymax As Double = Double.MinValue
            With ZedCurve
                .Clear()
                Dim DtaY As New Generic.SortedList(Of Date, Double)
                Dim dtX, dtY As DataTable
                If _Table.TableType = clsTable.enumTableType.Calibration Then
                    dtY = _Table.Calibration.GetData(_StationID, _PCode, clsCalibration.enumDataType.Measured)
                    dtX = _Table.Calibration.GetData(_StationIDX, _PCodeX, clsCalibration.enumDataType.Simulated)
                    Select Case _Table.Calibration.TimeAggregation
                        Case clsCalibration.enumTimeAgg.Hour : TimeWindow = enumTimeWindow._1_Hour
                        Case clsCalibration.enumTimeAgg.Day : TimeWindow = enumTimeWindow._1_Day
                        Case clsCalibration.enumTimeAgg.Week : TimeWindow = enumTimeWindow._1_Week
                        Case clsCalibration.enumTimeAgg.Month : TimeWindow = enumTimeWindow._1_Month
                    End Select
                Else
                    dtY = _Table.GetResults(_StationID, _PCode)
                    dtX = _Table.GetResults(_StationIDX, _PCodeX)
                End If
                For Each r As DataRow In dtY.Rows
                    If Not DtaY.ContainsKey(r(0)) Then DtaY.Add(r(0), r(1))
                    Application.DoEvents()
                Next
                For Each r As DataRow In dtX.Rows
                    Dim tx As Date = r(0)
                    Dim txmin As Date = tx.AddMinutes(-_TimeWindowMin(_TimeWindow))
                    Dim txmax As Date = tx.AddMinutes(_TimeWindowMin(_TimeWindow))
                    Dim vx As Double = r(1)
                    'try to find closest y value within the time tolerance
                    Dim MinDiff As Double = Double.MaxValue, ClosestResult As Double = Double.MinValue

                    For Each ty As Date In DtaY.Keys
                        If ty >= txmin AndAlso ty <= txmax Then
                            Dim diff As Double = Math.Abs(tx.Subtract(ty).TotalDays)
                            If diff < MinDiff Then
                                MinDiff = diff
                                ClosestResult = DtaY(ty)
                            End If
                        End If
                        If ty > txmax Then Exit For
                    Next
                    If MinDiff <> Double.MaxValue Then
                        .AddPoint(vx, ClosestResult)
                        xmin = Math.Min(xmin, vx)
                        xmax = Math.Max(xmax, vx)
                        ymin = Math.Min(ymin, ClosestResult)
                        ymax = Math.Max(ymax, ClosestResult)
                        xsum += vx
                        ysum += ClosestResult
                    End If
                    'For Each ty As Date In DtaY.Keys
                    '    If ty >= txmin AndAlso ty <= txmax Then
                    '        .AddPoint(vx, DtaY(ty))
                    '        xmin = Math.Min(xmin, vx)
                    '        xmax = Math.Max(xmax, vx)
                    '        ymin = Math.Min(ymin, DtaY(ty))
                    '        ymax = Math.Max(ymax, DtaY(ty))
                    '        xsum += vx
                    '        ysum += DtaY(ty)
                    '    End If
                    '    If ty > txmax Then Exit For
                    '    'Debug.Print("{0}: {1}", i, ty)
                    '    i += 1
                    'Next
                    Application.DoEvents()
                Next
                If .NPts = 0 Then
                    xmin = 0 : xmax = 0 : ymin = 0 : ymax = 0
                    frmInfoMsg.ShowInfoMsg("Scatter Plot Warning", "No matching points were found for the combination of stations and PCodes you selected ({0}/{1} vs {2}/{3}) and the specified time window ({4} minutes). You may need to reselect your stations and/or PCodes or make the time window larger to allow points to be paired that are not exactly coincident in time.", _StationID, _PCode, _StationIDX, _PCodeX, _TimeWindowMin(_TimeWindow))
                End If
                _StatsText = String.Format("StationY: {0}\nPCodeY: {1}\nStationX: {9}\nPCodeX: {10}\nCount: {2}\nMin X Value: {3:N3}\nMax X Value: {4:N3}\nMean X Value: {5:N3}\nMin Y Value: {6:N3}\nMax Y Value: {7:N3}\nMean Y Value: {8:N3}", _StationID, _PCode, .NPts, xmin, xmax, xsum / Math.Max(.NPts, 1), ymin, ymax, ysum / Math.Max(.NPts, 1), _StationIDX, _PCodeX, Table.NumExceed * 100.0 / Math.Max(.NPts, 1))
            End With
        End If
        _NeedsReload = False
    End Sub
End Class

''' <summary>
''' This class was created to display probability plot information in the properties grid
''' </summary>
''' <remarks>Note: only Public properties are visible in the Properties grid</remarks>
Friend Class clsSeriesHistogram : Inherits clsSeries

    Private _HistOptions As enumHistOptions = enumHistOptions.Histogram
    Private _NumIntervals As Integer = 8

    Public Enum enumHistOptions
        Histogram
        Cumulative
        Histogram_Cumulative
    End Enum

    Friend Sub New(ByRef Zed As ZedGraph.ZedGraphControl)
        MyBase.New(Zed, enumGraphType.Histogram)
        LineType = enumLineType.Solid
        SymbolType = enumSymbolType.None
        If _Table IsNot Nothing AndAlso _Table.TableType = clsTable.enumTableType.Calibration Then _ZedCurveMeas.Line.Style = Drawing2D.DashStyle.Dash
    End Sub

    Public Overrides Property Label As String
        Get
            If _Table IsNot Nothing AndAlso _Table.TableType = clsTable.enumTableType.Calibration Then
                Return MyBase.Label.Replace(_Table.Calibration.LegendSuffixSimulated, "").Trim
            Else
                Return MyBase.Label
            End If
        End Get
        Set(value As String)
            If _Table IsNot Nothing AndAlso _Table.TableType = clsTable.enumTableType.Calibration Then
                _ZedCurve.Label.Text = IIf(value = "", "", value & " ") & _Table.Calibration.LegendSuffixSimulated
                _ZedCurveMeas.Label.Text = IIf(value = "", "", value & " ") & _Table.Calibration.LegendSuffixMeasured
            Else
                MyBase.Label = value
            End If
        End Set
    End Property

    ''' <summary>
    ''' Probability plot options (not currently used)
    ''' </summary>
    <DisplayName("1. Type of Plot"), Category("Histogram"), Description("Defines the type of histogram plot (read-only)"), [ReadOnly](True)> _
    Public Property HistOption() As enumHistOptions
        Get
            Return _HistOptions
        End Get
        Set(ByVal value As enumHistOptions)
            _HistOptions = value
            _NeedsReload = True
        End Set
    End Property

    ''' <summary>
    ''' Histogram bins
    ''' </summary>
    <DisplayName("2. Number of Intervals"), Category("Histogram"), Description("Defines the number of intervals (bins) the histograph is divided into")> _
    Public Property NumIntervals() As Integer
        Get
            Return _NumIntervals
        End Get
        Set(ByVal value As Integer)
            _NumIntervals = value
            _NeedsReload = True
            GetData()
            ActiveGraphTab.ZoomExtents()
        End Set
    End Property

    ''' <summary>
    ''' Only allow "line" format
    ''' </summary>
    Public Overrides Property LineType() As enumLineType
        Get
            Return enumLineType.None
        End Get
        Set(ByVal value As enumLineType)
            If value = enumLineType.None Then MyBase.LineType = enumLineType.Solid
            If _Table IsNot Nothing AndAlso _Table.TableType = clsTable.enumTableType.Calibration Then _ZedCurveMeas.Line.Style = Drawing2D.DashStyle.Dash
        End Set
    End Property

    ''' <summary>
    ''' Only allow "line" format
    ''' </summary>
    Public Overrides Property SymbolType() As enumSymbolType
        Get
            Return MyBase.SymbolType
        End Get
        Set(ByVal value As enumSymbolType)
            MyBase.SymbolType = enumSymbolType.None
        End Set
    End Property

    ''' <summary>
    ''' Retrieve data into curve given the current station id and pcode; also computes any curve fits that are requested
    ''' </summary>
    Friend Overrides Sub GetData()
        If _Table.TableType = clsTable.enumTableType.Calibration Then Exit Sub
        MyBase.GetData()

        If _StationID <> "" And _PCode <> "" Then
            Dim sum As Single = 0.0, min As Double = Double.MaxValue, max As Double = Double.MinValue, num As Integer = 0
            Dim result As New Generic.List(Of Double)
            Dim dr As DbDataReader = _Table.WRDBProject.DB.ExecuteReader("Select RCode,Result from {0} where Station_ID='{1}' and PCode='{2}'", _Table.WRDBProject.DB.AddBrackets(_Table.TableName), _StationID, _PCode)
            If dr Is Nothing Then Exit Sub
            While dr.Read
                Dim y As Double
                If Table.GetResult(_StationID, _PCode, Nothing, dr.GetValue(0), dr.GetValue(1), y) Then
                    num += 1
                    sum += y
                    min = Math.Min(min, y)
                    max = Math.Max(max, y)
                    result.Add(y)
                End If
                Application.DoEvents()
            End While
            dr.Close()

            ZedCurve.IsVisible = True
            Dim barwidth As Double = (max - min + 0.000001) / _NumIntervals
            Dim Bins(_NumIntervals - 1) As Integer

            'count number of values in each bin
            If min <> max Then
                For Each r As Double In Result
                    Dim interval As Integer = Int((r - min) / barwidth)
                    Bins(interval) += 1
                Next
            End If

            'draw bars
            With ZedCurve
                .Clear()
                .Label.Text = MyBase.Label & "-Histogram"
                For i As Integer = 0 To _NumIntervals - 1
                    Dim x As Double = min + barwidth * i
                    .AddPoint(x, 0)
                    .AddPoint(x, Bins(i) / num)
                    .AddPoint(x + barwidth, Bins(i) / num)
                    .AddPoint(x + barwidth, 0)
                Next
                If .NPts = 0 Then min = 0 : max = 0
                _StatsText = String.Format("Station: {0}\nPCode: {1}\nCount: {2}\nNon-Calc: {3}\nNon-Det: {4}\nExceed: {5} ({10:N1}%)\nMin Value: {6:N3}\nMax Value: {7:N3}\nMean Value: {8:N3}\nNum Bins: {9}", _StationID, _PCode, .NPts, Table.NumNonCalc, Table.NumNonDetect, Table.NumExceed, min, max, sum / Math.Max(.NPts, 1), _NumIntervals, Table.NumExceed * 100.0 / Math.Max(.NPts, 1))
            End With

        End If
        _NeedsReload = False
    End Sub

End Class

''' <summary>
''' This class was created to display probability plot information in the properties grid
''' </summary>
''' <remarks>Note: only Public properties are visible in the Properties grid</remarks>
Friend Class clsSeriesProbability : Inherits clsSeries

    'todo: display 7Q10 on chart

    Private _ProbOptions As enumProbOption

    Friend Sub New(ByRef Zed As ZedGraph.ZedGraphControl)
        MyBase.New(Zed, enumGraphType.Probability)
        LineType = enumLineType.None
        SymbolType = enumSymbolType.Circle
        ProbOption = enumProbOption.LessThan_Incr
    End Sub

    Friend Overrides Sub GetDefaultLabel()
        If _Table.TableType = clsTable.enumTableType.Calibration AndAlso Not _Table.Calibration.AutoSeriesPrefix Then
            Label = _Table.Calibration.SeriesPrefix 'will append suffix elsewhere
        Else
            Label = String.Format("{0} at {1} Plotting Probs", _PCode, _StationID)
        End If
    End Sub

    Public Overrides Property Label As String
        Get
            If _Table IsNot Nothing AndAlso _Table.TableType = clsTable.enumTableType.Calibration Then
                Return MyBase.Label.Replace(_Table.Calibration.LegendSuffixSimulated, "").Trim
            Else
                Return MyBase.Label
            End If
        End Get
        Set(value As String)
            If _Table IsNot Nothing AndAlso _Table.TableType = clsTable.enumTableType.Calibration Then
                _ZedCurve.Label.Text = IIf(value = "", "", value & " ") & _Table.Calibration.LegendSuffixSimulated
                _ZedCurveMeas.Label.Text = IIf(value = "", "", value & " ") & _Table.Calibration.LegendSuffixMeasured
            Else
                MyBase.Label = value
            End If
        End Set
    End Property

    Public Overrides Property SymbolType As clsSeries.enumSymbolType
        Get
            Return MyBase.SymbolType
        End Get
        Set(value As clsSeries.enumSymbolType)
            MyBase.SymbolType = value
            If _Table IsNot Nothing AndAlso _Table.TableType = clsTable.enumTableType.Calibration Then _ZedCurve.Symbol.IsVisible = False 'force symbols to be hidden for simulated
        End Set
    End Property

    Public Overrides Property Table As clsTable
        Get
            Return MyBase.Table
        End Get
        Set(value As clsTable)
            MyBase.Table = value
            If _Table IsNot Nothing AndAlso _Table.TableType = clsTable.enumTableType.Calibration Then _Zed.GraphPane.CurveList.Insert(1, _ZedCurveMeas)
        End Set
    End Property

    ''' <summary>
    ''' Probability plot options
    ''' </summary>
    <DisplayName("2a. Probability Option"), Category("(Data)"), Description("Controls how probabilities are plotted on the X Axis")> _
    Public Property ProbOption() As enumProbOption
        Get
            Return _ProbOptions
        End Get
        Set(ByVal value As enumProbOption)
            _ProbOptions = value
            Dim s As String = "Greater"
            If _ProbOptions = enumProbOption.LessThan_Decr Or _ProbOptions = enumProbOption.LessThan_Incr Then s = "Less"
            _Zed.GraphPane.XAxis.Title.Text = String.Format("Probability Percent {0} Than", s)
            _Zed.GraphPane.XAxis.Scale.IsReverse = False
            If _ProbOptions = enumProbOption.LessThan_Decr Or _ProbOptions = enumProbOption.GreaterThan_Decr Then _Zed.GraphPane.XAxis.Scale.IsReverse = True
            _NeedsReload = True
            '_CurveFit = enumCurveFit.None
        End Set
    End Property

    'Tim Wool wanted to be able to override default use of symbols only on probability plots, so let following revert to parent class action

    ' ''' <summary>
    ' ''' Only allow "symbols" format
    ' ''' </summary>
    'Public Overrides Property LineType() As enumLineType
    '    Get
    '        Return enumLineType.None
    '    End Get
    '    Set(ByVal value As enumLineType)
    '        If IsCalc Then MyBase.LineType = value Else MyBase.LineType = enumLineType.None
    '    End Set
    'End Property

    ' ''' <summary>
    ' ''' Only allow "symbols" format
    ' ''' </summary>
    'Public Overrides Property SymbolType() As enumSymbolType
    '    Get
    '        Return MyBase.SymbolType
    '    End Get
    '    Set(ByVal value As enumSymbolType)
    '        If IsCalc Then MyBase.SymbolType = value Else MyBase.SymbolType = IIf(value = enumSymbolType.None, enumSymbolType.Square, value)
    '    End Set
    'End Property

    ''' <summary>
    ''' Retrieve data into curve given the current station id and pcode; also computes any curve fits that are requested
    ''' </summary>
    Friend Overrides Sub GetData()
        MyBase.GetData()

        If _StationID <> "" And _PCode <> "" Then
            Dim min, max, avg As Double

            If _Table.TableType = clsTable.enumTableType.Calibration Then
                _StatsText = String.Format("Station: {0}\nPCode: {1}", _StationID, _PCode)
                AddPoints(_Table.Calibration.GetData(_StationID, _PCode, clsCalibration.enumDataType.Simulated), _ZedCurve, min, max, avg)
                With _Table.Calibration.tblSimulated
                    _StatsText &= String.Format("\n*{0} Values*\nCount: {1}\nNon-Calc: {2}\nNon-Det: {3}\nExceed: {4} ({8:N1}%)\nMin Value: {5:N3}\nMax Value: {6:N3}\nMean Value: {7:N3}",
                                                _Table.Calibration.LegendSuffixSimulated, _ZedCurve.NPts, .NumNonCalc, .NumNonDetect, .NumExceed, min, max, avg, .NumExceed * 100.0 / Math.Max(_ZedCurve.NPts, 1))
                End With
                AddPoints(_Table.Calibration.GetData(_StationID, _PCode, clsCalibration.enumDataType.Measured), _ZedCurveMeas, min, max, avg)
                With _Table.Calibration.tblMeasured
                    _StatsText &= String.Format("\n*{0} Values*\nCount: {1}\nNon-Calc: {2}\nNon-Det: {3}\nExceed: {4} ({8:N1}%)\nMin Value: {5:N3}\nMax Value: {6:N3}\nMean Value: {7:N3}",
                                                _Table.Calibration.LegendSuffixMeasured, _ZedCurveMeas.NPts, .NumNonCalc, .NumNonDetect, .NumExceed, min, max, avg, .NumExceed * 100.0 / Math.Max(_ZedCurveMeas.NPts, 1))
                End With
            Else
                AddPoints(_Table.GetResults(_StationID, _PCode), _ZedCurve, min, max, avg)
                _StatsText = String.Format("Station: {0}\nPCode: {1}\nCount: {2}\nNon-Calc: {3}\nNon-Det: {4}\nExceed: {5} ({9:N1}%)\nMin Value: {6:N3}\nMax Value: {7:N3}\nMean Value: {8:N3}",
                                           _StationID, _PCode, _ZedCurve.NPts, Table.NumNonCalc, Table.NumNonDetect, Table.NumExceed, min, max, avg, Table.NumExceed * 100.0 / Math.Max(_ZedCurve.NPts, 1))
            End If
        End If
        _NeedsReload = False
    End Sub

    Private Sub AddPoints(dt As DataTable, zc As ZedGraph.LineItem, ByRef min As Double, ByRef max As Double, ByRef avg As Double)
        zc.Clear()
        dt.DefaultView.Sort = "Result"
        Dim sum As Single = 0
        min = Single.MaxValue
        max = Single.MinValue
        For r As Integer = 0 To dt.Rows.Count - 1
            Dim y As Double = dt.DefaultView(r)(1)
            sum += y
            min = Math.Min(min, y)
            max = Math.Max(max, y)
            Dim Prob As Double = (r + 1.0) / (dt.Rows.Count + 1)
            If _ProbOptions = enumProbOption.GreaterThan_Decr Or _ProbOptions = enumProbOption.GreaterThan_Incr Then Prob = 1 - Prob
            zc.AddPoint(Prob, y)
            Application.DoEvents()
        Next
        If zc.NPts = 0 Then min = 0 : max = 0 : avg = 0 Else avg = sum / zc.NPts
    End Sub

End Class

''' <summary>
''' This class was created to display flow/load-duration plot information in the properties grid
''' </summary>
''' <remarks>Note: only Public properties are visible in the Properties grid</remarks>
Friend Class clsSeriesFlowDuration : Inherits clsSeries

    Friend Sub New(ByRef Zed As ZedGraph.ZedGraphControl)
        MyBase.New(Zed, enumGraphType.FlowDuration)
        LineType = enumLineType.Solid
        SymbolType = enumSymbolType.None
    End Sub

    ''' <summary>
    ''' Retrieve data into curve given the current station id and pcode; also computes any curve fits that are requested
    ''' </summary>
    Friend Overrides Sub GetData()
        If _Table.TableType = clsTable.enumTableType.Calibration Then Exit Sub
        MyBase.GetData()

        If _StationID <> "" And _PCode <> "" Then
            With ZedCurve
                .Clear()
                Dim sum As Single = 0, min As Single = Single.MaxValue, max As Single = Single.MinValue
                Dim Result As New Generic.List(Of Double)
                If Not _Table.WRDBProject.DB.TableExists(_Table.TableName) And Not _Table.WRDBProject.DB.ViewExists(_Table.TableName) Then Exit Sub
                Dim dr As DbDataReader = _Table.WRDBProject.DB.ExecuteReader("SELECT RCode,Result FROM {0} WHERE Station_ID='{1}' AND PCode='{2}'", _Table.WRDBProject.DB.AddBrackets(_Table.TableName), _StationID, _PCode)
                If dr Is Nothing Then Exit Sub
                While dr.Read
                    Dim y As Double
                    If Table.GetResult(_StationID, _PCode, Nothing, dr.GetValue(0), dr.GetValue(1), y) Then
                        Result.Add(y)
                        sum += y
                        min = Math.Min(min, y)
                        max = Math.Max(max, y)
                    End If
                    Application.DoEvents()
                End While
                dr.Close()
                dr = Nothing
                Result.Sort()
                For i As Integer = 0 To Result.Count - 1
                    Dim Pct As Double = Math.Round((100.0 - CDbl(i) / (Result.Count - 1) * 100.0), 3)
                    .AddPoint(Pct, Result(i))
                    Application.DoEvents()
                Next
                If .NPts = 0 Then min = 0 : max = 0
                _StatsText = String.Format("Station: {0}\nPCode: {1}\nCount: {2}\nNon-Calc: {3}\nNon-Det: {4}\nExceed: {5} ({9:N1}%)\nMin Value: {6:N3}\nMax Value: {7:N3}\nMean Value: {8:N3}", _StationID, _PCode, .NPts, Table.NumNonCalc, Table.NumNonDetect, Table.NumExceed, min, max, sum / Math.Max(.NPts, 1), Table.NumExceed * 100.0 / Math.Max(.NPts, 1))
            End With
            MyBase.Label = String.Format("{0} at {1} Load-Duration", _PCode, _StationID)
        End If
        _NeedsReload = False
    End Sub

End Class

